package org.smoozik.services.sound;


import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ShortBuffer;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.FloatControl;
import javax.sound.sampled.Line;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.Mixer;
import javax.sound.sampled.Port.Info;
import javax.sound.sampled.SourceDataLine;

public class SoundManager {

	// The following are general instance variables
	// used to create a SourceDataLine object.
	AudioFormat audioFormat;
	AudioInputStream audioInputStream;
	SourceDataLine sourceDataLine;

	// The following are audio format parameters.
	// They may be modified by the signal generator
	// at runtime. Values allowed by Java
	// SDK 1.4.1 are shown in comments.
	float sampleRate = 16000.0F;
	// Allowable 8000,11025,16000,22050,44100
	int sampleSizeInBits = 16;
	// Allowable 8,16
	int channels = 1;
	// Allowable 1,2
	boolean signed = true;
	// Allowable true,false
	boolean bigEndian = true;
	// Allowable true,false

	// A buffer to hold two seconds monaural and one
	// second stereo data at 16000 samp/sec for
	// 16-bit samples
	// byte audioData[] = new byte[16000 * 4];
	// byte audioData[] = new byte[160 * 4];

	public static Double DEMI_TON = Math.sqrt(Math.sqrt(Math.cbrt(2)));
	public static double LA_440 = 220;
	public static double DO = LA_440 * Math.pow(DEMI_TON, 3);

	private boolean stopped = false;

	public boolean isStopped() {
		return stopped;
	}

	public void setStopped(boolean stopped) {
		this.stopped = stopped;
	}

	// -------------------------------------------//
	public static void main(String args[]) throws Exception {
		// Mixer.Info[] mixers = AudioSystem.getMixerInfo();
		// System.out
		// .println("There are " + mixers.length + " mixer info objects");
		// for (Mixer.Info mixerInfo : mixers) {
		// System.out.println("mixer name: " + mixerInfo.getName());
		// Mixer mixer = AudioSystem.getMixer(mixerInfo);
		// Line.Info[] lineInfos = mixer.getTargetLineInfo(); // target, not
		// // source
		// for (Line.Info lineInfo : lineInfos) {
		// System.out.println("-----------------------Line.Info: "
		// + lineInfo);
		// Line line = null;
		// boolean opened = true;
		// try {
		// line = mixer.getLine(lineInfo);
		// opened = line.isOpen() || line instanceof Clip;
		// if (!opened) {
		// line.open();
		// }
		// FloatControl volCtrl = (FloatControl) line
		// .getControl(FloatControl.Type.VOLUME);
		// System.out.println("    volCtrl.getValue() = "
		// + volCtrl.getValue());
		// volCtrl.setValue(0);
		// new SoundManager().playNoteInNewThread(0);
		// Thread.sleep(2000);
		// volCtrl.setValue(1);
		// } catch (LineUnavailableException e) {
		// e.printStackTrace();
		// } catch (IllegalArgumentException iaEx) {
		// System.out.println("    " + iaEx);
		// } finally {
		// if (line != null && !opened) {
		// line.close();
		// }
		// }
		// }
		// }

		SoundManager sm = new SoundManager();
		sm.setVolume((float) 0.1);
		new SoundManager().playNoteInNewThread(0);
		Thread.sleep(2000);
		new SoundManager().playNoteInNewThread(5);
		Thread.sleep(2000);

		sm.setVolume(1);
		new SoundManager().playNoteInNewThread(0);
		Thread.sleep(2000);
		new SoundManager().playNoteInNewThread(5);

	}// end main
		// -------------------------------------------//

	public void setVolume(float f) {

		Mixer mixer = getMixer();

		Line line;
		if (mixer != null) {

			try {
				line = mixer.getLine(Info.HEADPHONE);

				boolean opened = line.isOpen() || line instanceof Clip;
				if (!opened) {
					line.open();
				}
				FloatControl volCtrl = (FloatControl) line
						.getControl(FloatControl.Type.VOLUME);

				volCtrl.setValue(f);
			} catch (LineUnavailableException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}

	public float getVolume() {
		float f = 0;

		Mixer mixer = getMixer();

		if (mixer != null) {

			Line line;
			try {
				line = mixer.getLine(Info.HEADPHONE);

				boolean opened = line.isOpen() || line instanceof Clip;
				if (!opened) {
					line.open();
				}
				FloatControl volCtrl = (FloatControl) line
						.getControl(FloatControl.Type.VOLUME);

				f = volCtrl.getValue();
			} catch (LineUnavailableException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}

		return f;
	}

	public Mixer getMixer() {

		Mixer.Info[] mixers = AudioSystem.getMixerInfo();
		Mixer mixer = null;
		for (Mixer.Info mixerInfo : mixers) {
			if (mixerInfo.getName().startsWith("Port Casque")) {
				// System.out
				// .println("mixerInfo.getName : " + mixerInfo.getName());
				// System.out.println("mixerInfo.getDescription : "
				// + mixerInfo.getDescription());
				// System.out.println("mixerInfo.getVendor : "
				// + mixerInfo.getVendor());
				// System.out.println("mixerInfo.getVersion : "
				// + mixerInfo.getVersion());
				mixer = AudioSystem.getMixer(mixerInfo);
				break;
			}
		}

		return mixer;

	}

	public static double getFreq(int index) {
		double d = DO;

		d = d * Math.pow(DEMI_TON, index);

		return d;
	}

	//
	// public static double getNote(double freq, int nbHalfTons) {
	// double d=freq;
	// int i=0;
	//
	// while(i<nbHalfTons) {
	// d = d*DEMI_TON;
	// i++;
	// }
	//
	// return d;
	// }

	public SoundManager() {// constructor
		// playOrFileData(523);

	}// end constructor
		// -------------------------------------------//

	public Thread playNoteInNewThread(int index) {
//		Thread t = new ListenThread(getFreq(index));
		Thread t = new ListenThread(index);
		t.start();
		return t;
	}

	// public void playNoteInNewThread(double freq) {
	// new ListenThread(freq).start();
	// }

	public void playMidiNote(int note) {
		Synthe synth = new Synthe(); // construction et initialisation du
		synth.play(0, note + 60, 128, 1000);
		
	}
	
	// This method plays or files the synthetic
	// audio data that has been generated and saved
	// in an array in memory.
	public void playOrFileData(double freq) {
		try {
			byte audioData[] = new byte[8000 * 4];
			new SynGen().getSyntheticData(audioData, freq);

			// Get an input stream on the byte array
			// containing the data
			InputStream byteArrayInputStream = new ByteArrayInputStream(
					audioData);

			// Get the required audio format
			audioFormat = new AudioFormat(sampleRate, sampleSizeInBits,
					channels, signed, bigEndian);

			// Get an audio input stream from the
			// ByteArrayInputStream
			audioInputStream = new AudioInputStream(byteArrayInputStream,
					audioFormat, audioData.length / audioFormat.getFrameSize());

			// Get info on the required data line
			DataLine.Info dataLineInfo = new DataLine.Info(
					SourceDataLine.class, audioFormat);

			// Get a SourceDataLine object
			sourceDataLine = (SourceDataLine) AudioSystem.getLine(dataLineInfo);
			// Decide whether to play the synthetic
			// data immediately, or to write it into
			// an audio file, based on the user
			// selection of the radio buttons in the
			// South of the GUI..
			// Create a thread to play back the data and
			// start it running. It will run until all
			// the data has been played back
			// new ListenThread().start();

			try {
				byte playBuffer[] = new byte[10];

				// Open and start the SourceDataLine
				sourceDataLine.open(audioFormat);
				sourceDataLine.start();

				int cnt;
//				int nbLoops = 0;

				// Transfer the audio data to the speakers
				while (stopped == false
						&& (cnt = audioInputStream.read(playBuffer, 0,
								playBuffer.length)) != -1) {
//					nbLoops ++;
					// Keep looping until the input read
					// method returns -1 for empty stream.
					if (cnt > 0) {
						// Write data to the internal buffer of
						// the data line where it will be
						// delivered to the speakers in real
						// time
						sourceDataLine.write(playBuffer, 0, cnt);
					}// end if
				}// end while

//				System.out.println("looped "+nbLoops);
//				
//				if(stopped) {
//					System.out.println("stopped in thread");
//				} else {
//					System.out.println("not stopped in thread");
//				}
					

				// Block and wait for internal buffer of the
				// SourceDataLine to become empty.
				sourceDataLine.drain();

				// Finish with the SourceDataLine
				sourceDataLine.stop();
				sourceDataLine.close();

			} catch (Exception e) {
				e.printStackTrace();
				System.exit(0);
			}// end catch

		} catch (Exception e) {
			e.printStackTrace();
			System.exit(0);
		}// end catch
	}// end playOrFileData
		// =============================================//

	// Inner class to play back the data that was
	// saved.
	class ListenThread extends Thread {
		// This is a working buffer used to transfer
		// the data between the AudioInputStream and
		// the SourceDataLine. The size is rather
		// arbitrary.
		byte playBuffer[] = new byte[16384];
		double freq;
		int note;

		public ListenThread(double freq) {
			this.freq = freq;
		}

		public ListenThread(int note) {
			this.note = note;
		}

		public void run() {
//			playOrFileData(freq);
			System.out.println("note " + note);
			playMidiNote(note);

		}// end run
	}// end inner class ListenThread
		// =============================================//

	// Inner signal generator class.

	// An object of this class can be used to
	// generate a variety of different synthetic
	// audio signals. Each time the getSyntheticData
	// method is called on an object of this class,
	// the method will fill the incoming array with
	// the samples for a synthetic signal.
	class SynGen {
		// Note: Because this class uses a ByteBuffer
		// asShortBuffer to handle the data, it can
		// only be used to generate signed 16-bit
		// data.
		ByteBuffer byteBuffer;
		ShortBuffer shortBuffer;
		int byteLength;

		void getSyntheticData(byte[] synDataBuffer, double freq) {
			// Prepare the ByteBuffer and the shortBuffer
			// for use
			byteBuffer = ByteBuffer.wrap(synDataBuffer);
			shortBuffer = byteBuffer.asShortBuffer();

			byteLength = synDataBuffer.length;
			tones(freq);

		}// end getSyntheticData method
			// -------------------------------------------//

		// This method generates a monaural triple-
		// frequency pulse that decays in a linear
		// fashion with time.
		void decayPulse(double freq) {
			channels = 1;// Java allows 1 or 2
			int bytesPerSamp = 2;// Based on channels
			sampleRate = 16000.0F;
			// Allowable 8000,11025,16000,22050,44100
			int sampLength = byteLength / bytesPerSamp;
			for (int cnt = 0; cnt < sampLength; cnt++) {
				// The value of scale controls the rate of
				// decay - large scale, fast decay.
				double scale = 2 * cnt;
				if (scale > sampLength)
					scale = sampLength;
				double gain = 16000 * (sampLength - scale) / sampLength;
				double time = cnt / sampleRate;
				// double freq = 499.0;// an arbitrary freq
				double sinValue = (Math.sin(2 * Math.PI * freq * time)
						+ Math.sin(2 * Math.PI * (freq / 1.8) * time) + Math
						.sin(2 * Math.PI * (freq / 1.5) * time)) / 3.0;
				shortBuffer.put((short) (gain * sinValue));
			}// end for loop
		}// end method decayPulse
			// -------------------------------------------//

		// This method generates a monaural tone
		// consisting of the sum of three sinusoids.
		void tones(double freq) {
			channels = 1;// Java allows 1 or 2
			// Each channel requires two 8-bit bytes per
			// 16-bit sample.
			int bytesPerSamp = 2;
			sampleRate = 16000.0F;
			// Allowable 8000,11025,16000,22050,44100
			int sampLength = byteLength / bytesPerSamp;
			for (int cnt = 0; cnt < sampLength; cnt++) {
				double time = cnt / sampleRate;
				double sinValue = (Math.sin(2 * Math.PI * freq * time)
						+ Math.sin(2 * Math.PI * (freq / 1.8) * time) + Math
						.sin(2 * Math.PI * (freq / 1.5) * time)) / 3.0;
				shortBuffer.put((short) (16000 * sinValue));
			}// end for loop
		}// end method tones
			// -------------------------------------------//

		// -------------------------------------------//
	}// end SynGen class
		// =============================================//

}// end outer class SoundManager.java